-
Notifications
You must be signed in to change notification settings - Fork 244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create amp-validator-rules package #377
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, dropped some comments! I think either @sebastianbenz or @andreban should review as well. (I also don't have merge rights.)
packages/validator-query/README.md
Outdated
#### ES Module (Browser) | ||
|
||
```javascript | ||
import { load } from 'amp-toolbox-validator-query'; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
packages/validator-query/README.md
Outdated
#### CommonJs (Node) | ||
|
||
```javascript | ||
const { load } = require('amp-toolbox-validator-query'); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
packages/validator-query/README.md
Outdated
* `noCache`: true to always fetch latest rules (by default, subsequent calls to `load` reuse the same result). | ||
* `rules`: object to use locally specified rules instead of fetching them from the AMP CDN. | ||
* `url`: override the URL where validator rules are fetched from. | ||
* `source`: one of `'local'` (load rules from local file named "validator.json"), `'remote'` (fetch rules from CDN) or `'auto'` which is the default (tries looking for the local file first, then tries to fetch from CDN). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not for this PR, but we should create some standard way to handle these options (noCache
is also related).
Other places where a similar mechanism exists:
- the linter, which essentially defaults to
auto
- runtime-version, which uses onebehindfetch
- the validator (defaults to network)
- … probably others
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Migrated this to its own issue: #378
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW: this is the only toolbox package that works with validator.json
(as opposed to validator.js
).
In theory, this is meant to be a low-level interface to that file, so no other tool should ever need to load validator.json
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to Michael's comment
@@ -0,0 +1,27 @@ | |||
{ | |||
"name": "@ampproject/validator-query", |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
packages/validator-query/index.js
Outdated
return cached; | ||
} | ||
|
||
module.exports = { load }; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
@@ -0,0 +1,171 @@ | |||
/** |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's not obvious from the log, but if I try to make a test break (e.g. change one expected result), npm test
complains, so I'm quite confident it runs.
The pattern also matches this file if I'm reading it correctly (**
should also work for .
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, comment withdrawn!
*/ | ||
|
||
const https = require('https'); | ||
const {promisify} = require('util'); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! Left a few comments inline.
Two higher level comments:
- What are typical use cases for API? Are these covered by the two methods we provide? Would it make sense to always fetch rules per format? e.g.:
ValidationRules.fetch('AMPHTML')
? - Suggestion: can we rename the project to toolbox-validation-rules? I think this better explains what it does.
packages/validator-query/README.md
Outdated
#### ES Module (Browser) | ||
|
||
```javascript | ||
import { load } from '@ampproject/toolbox-validator-query'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use a more descriptive name? Suggestion:
import { ValidationRules } from '@ampproject/toolbox-validator-query';
const validationRules = await ValidationRules.fetch()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I'm OK with renaming, but I have two problems with that:
fetch
sounds like it misrepresents what that method does since it may not fetch anything, depending on the options provided.- What other methods would
ValidationRules
have? I'm kind of reluctant to expose theAmpValidatorQuery
class itself since the end user shouldn't need to instantiate it on their own and I don't see any other functions we'd need. That said, I'm OK with havingValidationRules
an object with a single method if you feel that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I prefer to export a class is that it becomes easier to test:
Users will use:
const validationRules = await ValidationRules.fetch()
For testing you can do:
const validationRules = new ValidationRules(fetchMock).fetch()
fetch
was just a suggestion. What I like about it is that it makes it clear that this might perform a network request (as opposed to ValidationRules.get()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I don't see a reason to make the class public since testing is already possible by directly including (see AmpValidatorRulesSpec.js
) - so this is the closest we can get to package-private constructor. The user can also specify a mock to the load
function via the rules
parameter (e.g. load({ rules: { ... } })
). Exposing the class makes me feel like it can cause a lot of confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even better if we don't need to export a class, however, we still should export an object in that case:
module.exports = {
fetch: ... (or whatever name we decide on)
}
This is to keep the possibility to add more methods in the future and to be consistent with the other modules, e.g.:
const AmpOptimizer = require('@ampproject/toolbox-optimizer');
const ampOptimizer = AmpOptimizer.create();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, renamed to fetch
and ensured it's exported as an object (it was already, but I made it clear in the readme).
packages/validator-query/README.md
Outdated
* `noCache`: true to always fetch latest rules (by default, subsequent calls to `load` reuse the same result). | ||
* `rules`: object to use locally specified rules instead of fetching them from the AMP CDN. | ||
* `url`: override the URL where validator rules are fetched from. | ||
* `source`: one of `'local'` (load rules from local file named "validator.json"), `'remote'` (fetch rules from CDN) or `'auto'` which is the default (tries looking for the local file first, then tries to fetch from CDN). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to Michael's comment
return JSON.parse(data); | ||
} | ||
|
||
async function loadLocal() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should move this functionality into the core package (we need it for optimizer as well) and find a common approach. As a start, I'd suggest only using OneBehindFetch and add caching later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, sort of. I consider the current loadRules.js
temporary, so when we figure out what to do with #378, most of it will probably go away.
}); | ||
|
||
function makeQuery(rules) { | ||
return new AmpValidatorQuery( |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unit test doesn't do any network requests - in fact, the AmpValidatorQuery
can't fetch the rules, it expects them to be provided (load
fetches them and passes them to AmpValidatorQuery
). Here the rules are provided as a local object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I was one step ahead in my mind.
packages/validator-query/README.md
Outdated
@@ -0,0 +1,57 @@ | |||
# AMP-Toolbox Validator Query | |||
|
|||
Makes it easier to query published AMP Validator rules and extract information |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
packages/validator-query/README.md
Outdated
const query = load(); | ||
|
||
// Get all tag names used in AMP for Email | ||
const names = query.getTagsForFormat('AMP4EMAIL').map(tag => tag.tagName); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Linked to relevant proto definitions and added examples for all methods.
this.initRules_(rules); | ||
} | ||
|
||
getTagsForFormat(format, transformed) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc
}); | ||
} | ||
|
||
getExtensionsForFormat(format) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect this method to return an array. Why return an object instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- There shouldn't ever be more than one extension with the same name and...
- ...I assumed the query would usually be "give me information about amp-bind", so an object seems more appropriate. Also, it's cheap to transform an object back into an array with
Object.keys
and/orObject.values
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about turning this into two different APIs?
const ampBindRule = validationRules.getExtension('amp-bind');
const extensions = validationRules.getExtensions() <- returns an array containing all extensions
const mailExtensions = validationRules.getExtensions({format: 'AMP4EMAIL'}) <- with optional format parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed getExtensions
and instead exposed it as an extensions
property and added getExtension
which follows your suggestion.
* limitations under the License. | ||
*/ | ||
|
||
class AmpValidatorQuery { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
if (transformed) { | ||
return this.checkEntityFormat_(entity, 'transformed'); | ||
} | ||
if (entity.enabledBy && entity.enabledBy.includes('transformed')) { |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking back, I wonder if I can just reuse checkEntityFormat_
and call it as !this.checkEntityFormat_(entity, 'transformed')
...
Will need to give it some thought.
All comments should be addressed.
Hard to say: I went with a conservative API that I hope will evolve over time as we identify more use-cases.
I'd be OK with that, but also don't see any advantages in that approach, especially considering the underlying data is the same for all formats.
Done. |
packages/validator-rules/README.md
Outdated
|
||
```javascript | ||
// Loads the validator rules remotely with default options | ||
const rules = validatorRules.fetch(); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
packages/validator-rules/README.md
Outdated
console.log(tags.map(tag => tag.tagName)); | ||
|
||
// Get information about an extension | ||
const ext = rules.getExtension('amp-carousel', 'AMP4EMAIL'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to provide the format here? This seems counterintuitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just how validator rules are written - there's more than one extension definition with the same name, but different formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation uses a different order getExtension(format, extension)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch, good catch. Fixed.
packages/validator-rules/README.md
Outdated
|
||
Specifically: | ||
|
||
- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
packages/validator-rules/index.js
Outdated
let cached = null; | ||
|
||
async function load(opt) { | ||
opt = opt || {}; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
packages/validator-rules/index.js
Outdated
} | ||
|
||
let rules = opt.rules; | ||
delete opt.rules; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't even remember why I had it there. Thanks for flagging, removed.
packages/validator-rules/README.md
Outdated
console.log(tags.map(tag => tag.tagName)); | ||
|
||
// Get information about an extension | ||
const ext = rules.getExtension('amp-carousel', 'AMP4EMAIL'); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
for (const attrList of tag.attrLists) { | ||
tag.attrs.push(...this.attrLists_[attrList]); | ||
} | ||
delete tag.attrLists; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
async function loadRules({source, url}) { | ||
url = url || VALIDATOR_RULES_URL; | ||
switch (source) { | ||
case 'local': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of configuring this via options, you could also provide to methods fetch
and loadFile
on the ValidationRules object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel the functionality is not different enough to vouch having two different functions and that also prevents me from easily having the default option which is "try local if possible".
That said, maybe source
is not needed if the url
is a local file, i.e. use something like
amp-toolbox/packages/cli/lib/io.js
Line 25 in 533464d
async function loadUrlOrFile(urlOrPath) { |
Ideally, whatever the resolution of #378 ends up being will replace this file anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I'd suggest removing loadLocal until we settled on a common approach. Once it's out there it's harder to deprecate and I don't think that the feature is critical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point, done.
Addressed all comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Few nits.
@@ -60,7 +61,7 @@ The rules used closely follow the proto definitions from [validator.proto](https | |||
|
|||
Specifically: | |||
|
|||
- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643) | |||
- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643), the same format used by `https://cdn.ampproject.org/v0/validator.json` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: turn this into a link
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think that makes sense, clicking that link is probably not very useful, it's more meaningful as an identifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd want to click it :-). But I agree unformatted JSON is not helpful.
@@ -158,15 +158,21 @@ class AmpValidatorRules { | |||
.filter((tag) => !tag.extensionSpec) | |||
.map((tag) => { | |||
tag.attrs = tag.attrs || []; | |||
|
|||
// Merge global attribute lists into atrrs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/atrrs/attrLists
Your comment explains what you're doing, but I get that from the code. I'm more interested in the why (which I don't understand)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried rewriting. The key thing is that attrLists
contains a list of IDs that are looked up from the top-level attrLists property. It doesn't contain any real attribute definition. This bit of code merges the real attribute definitions (from the global object) into attrs
.
Let me know if the current comment makes sense to convey that.
async function loadRules({source, url}) { | ||
url = url || VALIDATOR_RULES_URL; | ||
switch (source) { | ||
case 'local': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I'd suggest removing loadLocal until we settled on a common approach. Once it's out there it's harder to deprecate and I don't think that the feature is critical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One nit
packages/validator-rules/README.md
Outdated
console.log(tags.map(tag => tag.tagName)); | ||
|
||
// Get information about an extension | ||
const ext = rules.getExtension('amp-carousel', 'AMP4EMAIL'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation uses a different order getExtension(format, extension)
.
@@ -60,7 +61,7 @@ The rules used closely follow the proto definitions from [validator.proto](https | |||
|
|||
Specifically: | |||
|
|||
- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643) | |||
- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643), the same format used by `https://cdn.ampproject.org/v0/validator.json` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd want to click it :-). But I agree unformatted JSON is not helpful.
return req.json(); | ||
} | ||
|
||
async function loadRules({url}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I learned something, was't aware that restructuring works for params.
d9e83d6
to
9d85829
Compare
Whoop whoop! Thanks! |
This creates a new tool, amp-validator-query, that makes it easier to query AMP validator JSON rules to extract information.